// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#pragma once

#include <assert.h>

// The state of a thread has two axes. One is its arc from birth to
// death, expressed via its Lifecycle value. The other its current
// exception handler state, expressed via its Exception value.

// This class exists to encapsulate all legal state transitions, so
// generally other assertions about the state of a thread prior to
// transitioning are not necessary.

// Only RUNNING, SUSPENDED, or DYING threads may participate in
// exception handling.

class ThreadState {
public:
    // The only legal transition that isn't from top-to-bottom occurs
    // when a thread is resumed after being suspended.
    enum class Lifecycle {
        // The ThreadDispatcher has been allocated, but not yet
        // associated to a thread_t or an aspace.
        INITIAL,

        // The ThreadDispatcher is now associated to its underlying
        // thread_t and the containing process's address space, and is
        // waiting to be run.
        INITIALIZED,

        // The thread is running.
        RUNNING,

        // The thread is currently suspended.
        // Note that suspension is orthogonal to being "in an exception".
        // A thread may be both suspended and in an exception, and the thread
        // does not "resume" execution until it is resumed from both the
        // suspension and the exception.
        SUSPENDED,

        // The thread is going to die. It may still be interacting
        // with exception handling state.
        DYING,

        // The thread is being dissociated from all of its state, and
        // no more interaction with userspace (including exception
        // handlers) is possible.
        DEAD,
    };

    // IDLE threads become UNPROCESSED. UNPROCESSED threads are told
    // to either RESUME or TRY_NEXT in a loop until they are killed
    // (no more exception handlers) or resumed, in which case they
    // become IDLE again.
    enum class Exception {
        // There's no pending exception.
        IDLE,

        // The thread is waiting for the pending exception to be
        // processed.
        UNPROCESSED,

        // The exception has been processed, and the next exception
        // handler should be queried.
        TRY_NEXT,

        // The exception has been processed, and the thread should
        // resume.
        RESUME,
    };

    Lifecycle lifecycle() const {
        switch (value_) {
        case Value::INITIAL_IDLE:
            return Lifecycle::INITIAL;
        case Value::INITIALIZED_IDLE:
            return Lifecycle::INITIALIZED;
        case Value::RUNNING_IDLE:
        case Value::RUNNING_UNPROCESSED:
        case Value::RUNNING_TRY_NEXT:
        case Value::RUNNING_RESUME:
            return Lifecycle::RUNNING;
        case Value::SUSPENDED_IDLE:
        case Value::SUSPENDED_UNPROCESSED:
        case Value::SUSPENDED_TRY_NEXT:
        case Value::SUSPENDED_RESUME:
            return Lifecycle::SUSPENDED;
        case Value::DYING_IDLE:
        case Value::DYING_UNPROCESSED:
        case Value::DYING_TRY_NEXT:
        case Value::DYING_RESUME:
            return Lifecycle::DYING;
        case Value::DEAD_IDLE:
            return Lifecycle::DEAD;
        default:
            ASSERT(false);
        }
    }

    // Only RUNNING, SUSPENDED, and DYING threads have meaningful
    // exception state.
    Exception exception() const {
        switch (value_) {
        case Value::RUNNING_IDLE:
        case Value::SUSPENDED_IDLE:
        case Value::DYING_IDLE:
            return Exception::IDLE;
        case Value::RUNNING_UNPROCESSED:
        case Value::SUSPENDED_UNPROCESSED:
        case Value::DYING_UNPROCESSED:
            return Exception::UNPROCESSED;
        case Value::RUNNING_TRY_NEXT:
        case Value::SUSPENDED_TRY_NEXT:
        case Value::DYING_TRY_NEXT:
            return Exception::TRY_NEXT;
        case Value::RUNNING_RESUME:
        case Value::SUSPENDED_RESUME:
        case Value::DYING_RESUME:
            return Exception::RESUME;
        case Value::INITIALIZED_IDLE:
        case Value::DEAD_IDLE:
            // Someone could have, for example, requested zx_info_thread_t.
            return Exception::IDLE;
        default:
            ASSERT(false);
        }
    }

    void set(Lifecycle lifecycle) {
        switch (lifecycle) {
        case Lifecycle::INITIAL:
            DEBUG_ASSERT(false);
            return;
        case Lifecycle::INITIALIZED:
            switch (value_) {
            case Value::INITIAL_IDLE:
                value_ = Value::INITIALIZED_IDLE;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Lifecycle::RUNNING:
            switch (value_) {
            case Value::INITIALIZED_IDLE:
            case Value::SUSPENDED_IDLE:
                value_ = Value::RUNNING_IDLE;
                return;
            case Value::SUSPENDED_UNPROCESSED:
                value_ = Value::RUNNING_UNPROCESSED;
                return;
            case Value::SUSPENDED_TRY_NEXT:
                value_ = Value::RUNNING_TRY_NEXT;
                return;
            case Value::SUSPENDED_RESUME:
                value_ = Value::RUNNING_RESUME;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Lifecycle::SUSPENDED:
            switch (value_) {
            case Value::RUNNING_IDLE:
                value_ = Value::SUSPENDED_IDLE;
                return;
            case Value::RUNNING_UNPROCESSED:
                value_ = Value::SUSPENDED_UNPROCESSED;
                return;
            case Value::RUNNING_TRY_NEXT:
                value_ = Value::SUSPENDED_TRY_NEXT;
                return;
            case Value::RUNNING_RESUME:
                value_ = Value::SUSPENDED_RESUME;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Lifecycle::DYING:
            switch (value_) {
            case Value::RUNNING_IDLE:
            case Value::SUSPENDED_IDLE:
            case Value::DYING_IDLE:
                value_ = Value::DYING_IDLE;
                return;
            case Value::RUNNING_UNPROCESSED:
            case Value::SUSPENDED_UNPROCESSED:
            case Value::DYING_UNPROCESSED:
                value_ = Value::DYING_UNPROCESSED;
                return;
            case Value::RUNNING_TRY_NEXT:
            case Value::SUSPENDED_TRY_NEXT:
            case Value::DYING_TRY_NEXT:
                value_ = Value::DYING_TRY_NEXT;
                return;
            case Value::RUNNING_RESUME:
            case Value::SUSPENDED_RESUME:
            case Value::DYING_RESUME:
                value_ = Value::DYING_RESUME;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Lifecycle::DEAD:
            switch (value_) {
            case Value::DYING_IDLE:
            case Value::DYING_UNPROCESSED:
            case Value::DYING_TRY_NEXT:
            case Value::DYING_RESUME:
                value_ = Value::DEAD_IDLE;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        }
    }

    void set(Exception exception) {
        switch (exception) {
        case Exception::IDLE:
            switch (value_) {
            case Value::RUNNING_UNPROCESSED:
            case Value::RUNNING_TRY_NEXT:
            case Value::RUNNING_RESUME:
                value_ = Value::RUNNING_IDLE;
                return;
            case Value::SUSPENDED_UNPROCESSED:
            case Value::SUSPENDED_TRY_NEXT:
            case Value::SUSPENDED_RESUME:
                value_ = Value::SUSPENDED_IDLE;
                return;
            case Value::DYING_UNPROCESSED:
            case Value::DYING_TRY_NEXT:
            case Value::DYING_RESUME:
                value_ = Value::DYING_IDLE;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Exception::UNPROCESSED:
            switch (value_) {
            case Value::RUNNING_IDLE:
                value_ = Value::RUNNING_UNPROCESSED;
                return;
            case Value::SUSPENDED_IDLE:
                value_ = Value::SUSPENDED_UNPROCESSED;
                return;
            case Value::DYING_IDLE:
                value_ = Value::DYING_UNPROCESSED;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Exception::TRY_NEXT:
            switch (value_) {
            case Value::RUNNING_UNPROCESSED:
                value_ = Value::RUNNING_TRY_NEXT;
                return;
            case Value::SUSPENDED_UNPROCESSED:
                value_ = Value::SUSPENDED_TRY_NEXT;
                return;
            case Value::DYING_UNPROCESSED:
                value_ = Value::DYING_TRY_NEXT;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        case Exception::RESUME:
            switch (value_) {
            case Value::RUNNING_UNPROCESSED:
                value_ = Value::RUNNING_RESUME;
                return;
            case Value::SUSPENDED_UNPROCESSED:
                value_ = Value::SUSPENDED_RESUME;
                return;
            case Value::DYING_UNPROCESSED:
                value_ = Value::DYING_RESUME;
                return;
            default:
                DEBUG_ASSERT(false);
                return;
            }
        }
    }

private:
    enum class Value {
        INITIAL_IDLE,

        INITIALIZED_IDLE,

        RUNNING_IDLE,
        RUNNING_UNPROCESSED,
        RUNNING_TRY_NEXT,
        RUNNING_RESUME,

        SUSPENDED_IDLE,
        SUSPENDED_UNPROCESSED,
        SUSPENDED_TRY_NEXT,
        SUSPENDED_RESUME,

        DYING_IDLE,
        DYING_UNPROCESSED,
        DYING_TRY_NEXT,
        DYING_RESUME,

        DEAD_IDLE,
    };

    Value value_ = Value::INITIAL_IDLE;
};

const char* ThreadLifecycleToString(ThreadState::Lifecycle lifecycle);
